package ro.uaic.info.fmse.context.transformers.context;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import ro.uaic.info.fmse.context.transformers.enums.Multiplicity;
import ro.uaic.info.fmse.context.transformers.filters.AbstractFilter;
import ro.uaic.info.fmse.context.transformers.filters.LessNodesFilter;
import ro.uaic.info.fmse.context.transformers.generic.Edge;
import ro.uaic.info.fmse.context.transformers.generic.Node;
import ro.uaic.info.fmse.context.transformers.generic.Tree;
import ro.uaic.info.fmse.context.transformers.items.ConfigurationNode;
import ro.uaic.info.fmse.context.transformers.items.RuleNode;

public class ContextTransformer {

	public HashMap<Tree<ConfigurationNode>, Mapping> map = new HashMap<Tree<ConfigurationNode>, Mapping>();

	public List<Mapping> getMappings(Tree<ConfigurationNode> configuration,
			Tree<RuleNode> rule) {
		return getMapping(configuration.getRoot(), rule.getRoot());
	}

	private List<Mapping> getMapping(Node<ConfigurationNode> configuration,
			Node<RuleNode> rule) {
		// get all matchings
		List<Node<ConfigurationNode>> matchings = configuration
				.getNodesByLabel(rule.label);
		// System.out.println("MTCH: " + matchings);
		// choose valid matchings
		List<Node<ConfigurationNode>> valid_matchings = new ArrayList<Node<ConfigurationNode>>();
		for (Node<ConfigurationNode> cnode : matchings)
			if (Criteria.similar(cnode, rule)) {
				valid_matchings.add(cnode);
			}
		// System.out.println("MTCH: " + valid_matchings);
		// return no mapping because there are no valid mappings
		if (valid_matchings.size() == 0)
			return null;
		// create a mapping for each new valid matching
		if (rule.children == null) {
			// do not recurse
			List<Mapping> mappings = new ArrayList<Mapping>();
			Mapping m;
			for (Node<ConfigurationNode> cnode : valid_matchings) {
				m = new Mapping();
				m.add(new Pair(cnode.ID, rule.ID));
				mappings.add(m);
			}
			return mappings;
		} else {
			// recurse here and compose solutions
			List<Mapping> mappings = new ArrayList<Mapping>();
			for (Node<ConfigurationNode> cnode : valid_matchings) {
				// build the pair which will be added later in
				// its corresponding mapping
				Pair pair = new Pair(cnode.ID, rule.ID);
				// get the mappings for child of rule
				List<Mapping> mset = null;
				// Cartesian product for all rule.chidren
				for (Node<RuleNode> rnode : rule.children) {
					List<Mapping> sons = getMapping(cnode, rnode);
					if (sons != null)
						mset = times(mset, sons);
				}
				if (mset != null) {
					// distribute m to Cartesian product
					for (Mapping mm : mset) {
						// add the initial pair to mapping
						mm.add_front(pair);
						// add to final values
						mappings.add(mm);
					}
				}
			}
			return mappings;
		}
	}

	private List<Mapping> times(List<Mapping> mset, List<Mapping> sons) {
		if (mset == null && sons == null)
			return null;
		if (mset == null)
			return sons;
		List<Mapping> result = new ArrayList<Mapping>();
		for (Mapping m_mset : mset)
			for (Mapping s_sons : sons)
				result.add(Mapping.collapse(m_mset, s_sons));
		return result;
	}

	public List<Tree<ConfigurationNode>> getTrees(List<Mapping> mappings,
			Tree<ConfigurationNode> configuration, Tree<RuleNode> rule)
			throws CloneNotSupportedException {

		List<Tree<ConfigurationNode>> trees = null;
		for (Mapping m : mappings) {
			List<Tree<ConfigurationNode>> mtrees = getTrees(m, configuration,
					rule);
			// System.out.println("MTREES: " + mtrees);
			if (mtrees == null)
				continue;
			// store a map from tree to mapping
			for (Tree<ConfigurationNode> tree : mtrees) {
				map.put(tree, m);
			}
			if (trees == null)
				trees = mtrees;
			else {
				for (Tree<ConfigurationNode> tree : mtrees)
					trees.add(tree);
			}
		}

		return trees;
	}

	private List<Tree<ConfigurationNode>> getTrees(Mapping m,
			Tree<ConfigurationNode> configuration, Tree<RuleNode> rule)
			throws CloneNotSupportedException {

		List<Tree<ConfigurationNode>> paths = null;
		// System.out.println("M: " + m);
		for (Edge<RuleNode> edge : rule.getEdges()) {
			if (paths == null)
				paths = new ArrayList<Tree<ConfigurationNode>>();

			int from = m.getConfigNode(edge.getSource().ID);
			int to = m.getConfigNode(edge.getTarget().ID);

			if (from == -1 || to == -1)
				System.exit(1001);

			paths.add(getPath(from, to, configuration));
		}

		List<Tree<ConfigurationNode>> temp = getAllTrees(paths, configuration);

		// this filter avoids cases with occurrence > 1 and only one
		// occurence of the node. this is for not changing the semantics
		// of getAllVariants
		temp = filter(m, temp, rule);
		// System.out.println("LIST: " + temp);
		return temp != null ? temp : null;
	}

	private List<Tree<ConfigurationNode>> getAllTrees(
			List<Tree<ConfigurationNode>> paths,
			Tree<ConfigurationNode> configuration) {

		if (paths == null)
			return null;
		List<Tree<ConfigurationNode>> outtrees = null;

		// get all possible trees for each path

		// System.out.println("PATHS: " + paths);
		paths = sort(paths, configuration);

		// System.out.println("PATHS SORT: " + paths);
		for (Tree<ConfigurationNode> path : paths) {
			List<Tree<ConfigurationNode>> temptrees = new ArrayList<Tree<ConfigurationNode>>();
			if (outtrees == null)
				outtrees = getAllVariants(null, path);
			else {
				for (Tree<ConfigurationNode> t : outtrees) {
					List<Tree<ConfigurationNode>> temp = getAllVariants(t, path);
					if (temp != null)
						for (Tree<ConfigurationNode> tt : temp) {
							temptrees.add(tt);
						}
				}
				outtrees = temptrees;
			}
		}

		return outtrees != null ? outtrees : null;
	}

	private List<Tree<ConfigurationNode>> sort(
			List<Tree<ConfigurationNode>> paths,
			Tree<ConfigurationNode> configuration) {

		for (int i = 0; i < paths.size(); i++)
			for (int j = 0; j < paths.size(); j++) {
				int d1 = configuration.getDepth(paths.get(i).getRoot().ID);
				int d2 = configuration.getDepth(paths.get(j).getRoot().ID);
				// System.out.println("Compare: " + d1 + ", " + d2);
				if (d1 < d2) {
					// System.out.println("EVER HERE ");
					Tree<ConfigurationNode> temp = paths.get(i);
					paths.set(i, paths.get(j));
					paths.set(j, temp);
				}
			}

		return paths;

	}

	/* this function should generate all possibilities */
	@SuppressWarnings("unchecked")
	private List<Tree<ConfigurationNode>> getAllVariants(
			Tree<ConfigurationNode> tree, Tree<ConfigurationNode> path) {
		// System.out.println("TREE: " + tree);
		// System.out.println("PATH: " + path);
		Node<ConfigurationNode> node = path.getRoot();

		List<Tree<ConfigurationNode>> results = new ArrayList<Tree<ConfigurationNode>>();

		while (node != null) {
			if (tree == null) {
				results.add(path);
				break;
			} else {

				// search node in tree
				// System.out.println("SEARCH\nPATH: " + node + "\n\tin Tree\n"
				// + tree);
				Node<ConfigurationNode> tnode = tree.getNodeById(node.ID);

				if (tnode == null) {

					// System.out.println("NODE: " + tnode.ID);
					// node is not found => we reached the path leaf
					Node<ConfigurationNode> parent = tree
							.getNodeById(node.parent.ID);

					if (parent != null) {
						// create clone of tree, add node to id, add clone to
						// list,
						// stop;
						Tree<ConfigurationNode> clonetree;
						try {
							clonetree = (Tree<ConfigurationNode>) tree
									.getClone();
							// System.out.println("CLONE: " + clonetree +
							// "\n\n\n");
							clonetree.appendNode(node, parent.ID);
							results.add(clonetree);
						} catch (CloneNotSupportedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						break;
					}
				} else { // the node is already there
					if (tnode.property.multiplicity == Multiplicity.ANY
							|| tnode.property.multiplicity == Multiplicity.SOME) {
						// node is found and has multiplicity ANY|SOME
						// create a clone of the tree, collapse node.parent into
						// it,
						// add clone to list
						if (node.parent != null) {
							// avoid the case when node.parent == null
							Node<ConfigurationNode> parent = tree
									.getNodeById(node.parent.ID);
							if (parent != null) {
								Tree<ConfigurationNode> clonetree;
								try {
									clonetree = (Tree<ConfigurationNode>) tree
											.getClone();
									clonetree.appendNode(node, parent.ID);
									results.add(clonetree);
								} catch (CloneNotSupportedException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
							}
						}
					}
				}
			}

			// go down in path
			if (node.children != null)
				node = node.children.get(0);
			else
				break;
			// System.out.println("LOOP : " + node);
		}
		return results.size() > 0 ? results : null;
	}

	private Tree<ConfigurationNode> getPath(int configIDFrom, int configIDTo,
			Tree<ConfigurationNode> configuration)
			throws CloneNotSupportedException {
		// build a blind path: lose all data and focus only on the current node
		// specifications
		Node<ConfigurationNode> to = configuration.getNodeById(configIDTo);
		Node<ConfigurationNode> blindClone = to.blindClone();
		Node<ConfigurationNode> iterator = null;

		while (to != null && to.ID != configIDFrom) {
			to = to.parent;
			iterator = to.blindClone();
			iterator.appendChild(blindClone);
			blindClone = iterator;
		}

		Tree<ConfigurationNode> r = new Tree<ConfigurationNode>(blindClone);
		// System.out.println("RETURNED: " + r + "\n\n");
		return r;
	}

	private List<Tree<ConfigurationNode>> filter(Mapping m,
			List<Tree<ConfigurationNode>> temp, Tree<RuleNode> rule) {
		if (temp == null)
			return null;
		List<Tree<ConfigurationNode>> list = new ArrayList<Tree<ConfigurationNode>>();
		for (Tree<ConfigurationNode> t : temp)
			if (occ_valid(m, t, rule))
				list.add(t);
		return list.size() > 0 ? list : null;
	}

	private boolean occ_valid(Mapping m, Tree<ConfigurationNode> t,
			Tree<RuleNode> rule) {
		// System.out.println("MAP: " + m);
		// System.out.println("TREE: " + t);
		for (Pair p1 : m.get())
			for (Pair p2 : m.get()) {
				if (!p1.equals(p2)) {
					Node<ConfigurationNode> c1 = t.getNodeById(p1.cnode);
					Node<ConfigurationNode> c2 = t.getNodeById(p2.cnode);
					Node<RuleNode> r1 = rule.getNodeById(p1.rnode);
					Node<RuleNode> r2 = rule.getNodeById(p2.rnode);
					// System.out.println("Check: " + c1 + "\nwith\n" + c2 +
					// "\n\n\n");
					if (r1.label == r2.label
							&& r1.property.occurrence == r2.property.occurrence)
						if (c1.hasAncestor(c2) || c2.hasAncestor(c1))
							return false;
				}
			}
		return true;
	}

	private List<Tree<ConfigurationNode>> applyFilter(AbstractFilter filter,
			List<Tree<ConfigurationNode>> solutions) {
		return filter.filter(solutions);
	}

	public List<Tree<ConfigurationNode>> getContext(
			Tree<ConfigurationNode> configuration, Tree<RuleNode> rule)
			throws CloneNotSupportedException {
		List<Mapping> mapping = getMappings(configuration, rule);
		// System.out.println(mapping);
		List<Tree<ConfigurationNode>> trees = getTrees(mapping, configuration,
				rule);
		AbstractFilter lnf = new LessNodesFilter();
		trees = applyFilter(lnf, trees);
		return trees;
	}

	@SuppressWarnings("unused")
	private Tree<ConfigurationNode> getMinimalContext(
			Tree<ConfigurationNode> tree, Mapping m)
			throws CloneNotSupportedException {
		@SuppressWarnings("unchecked")
		Tree<ConfigurationNode> config = (Tree<ConfigurationNode>) tree
				.getClone();
		// Node<ConfigurationNode> it = config.getRoot();
		// int rID =
		// while (it != null)
		return config;
	}
}